package ro.peoplecentric.ctc.faces.util;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.faces.application.FacesMessage;
import javax.faces.application.NavigationHandler;
import javax.faces.application.ViewExpiredException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.ConcurrencyFailureException;

/**
 * ViewExpiredException exceptionHandler.
 *
 * @author pguzun
 */
public class ViewExpiredExceptionExceptionHandler extends ExceptionHandlerWrapper {

    private static final Logger LOGGER = LoggerFactory.getLogger(ViewExpiredExceptionExceptionHandler.class);
    private static final String RESOURCE_NAME = "msgs";
    private static final String VALIDATION = "ro.peoplecentric.ctc.faces.exception.validation";
    private static final String CONCURRENCY = "ro.peoplecentric.ctc.faces.exception.concurrency";

    private ExceptionHandler wrapped;

    public ViewExpiredExceptionExceptionHandler(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public ExceptionHandler getWrapped() {
        return this.wrapped;
    }

    @Override
    public void handle() {
        FacesContext fc = FacesContext.getCurrentInstance();
        ResourceBundle rb = fc.getApplication().getResourceBundle(FacesContext.getCurrentInstance(), RESOURCE_NAME);

        for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
            ExceptionQueuedEvent event = i.next();
            ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();

            Throwable t = getExceptionRootCause(context.getException());
            if (t instanceof ViewExpiredException) {
                ViewExpiredException vee = (ViewExpiredException) t;
                FacesContext facesContext = FacesContext.getCurrentInstance();
                Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
                Map<String, String> requestHeaderMap = facesContext.getExternalContext().getRequestHeaderMap();
                NavigationHandler navigationHandler = facesContext.getApplication().getNavigationHandler();
                try {
                    String ajaxHeader = requestHeaderMap.get("Faces-Request");

                    if ("partial/ajax".equals(ajaxHeader)) {
                        // ajax redirect
                        try {
                            facesContext.getExternalContext().redirect(requestHeaderMap.get("referer"));
                        } catch (IOException e) {
                            LOGGER.error("", e);
                        }
                    } else {
                        // Push some useful stuff to the request scope for use in the page
                        requestMap.put("currentViewId", vee.getViewId());
                        navigationHandler.handleNavigation(facesContext, null, "/");
                        facesContext.renderResponse();
                    }
                } finally {
                    i.remove();
                }
            } else if (t instanceof ConcurrencyFailureException) {
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, rb.getString(CONCURRENCY), null);
                fc.addMessage(null, facesMessage);
                i.remove();
            } else if (t instanceof ConstraintViolationException) {
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, rb.getString(VALIDATION), null);
                fc.addMessage(null, facesMessage);

                Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) t).getConstraintViolations();
                for (ConstraintViolation<?> v : violations) {
                    facesMessage = new FacesMessage(FacesMessage.SEVERITY_WARN, rb.getString(v.getMessage()), null);
                    fc.addMessage(null, facesMessage);
                }

                i.remove();
            }
        }

        // At this point, the queue will not contain any ViewExpiredEvents. Therefore, let the parent handle them.
        getWrapped().handle();
    }

    /**
     * Recursive method to get the most inner exception root cause
     *
     * @param t
     * @return
     */
    private Throwable getExceptionRootCause(Throwable t) {
        if (t == null) {
            return null;
        }

        Throwable root = t.getCause();
        if (root == null) {
            return t;
        } else {
            return getExceptionRootCause(root);
        }

    }
}
